/*
 * wrapendpoint.cxx
 *
 * OpenH323 Wrapper Library
 *
 * Copyright (c) 2002-2005 InAccess Networks
 * Michalis Manousos <manousos@inaccessnetworks.com>
 * Dimitris Economou <decon@inaccessnetworks.com>
 *
 * This file is part of "H.323 support for ASTERISK"
 *
 * "H.323 support for ASTERISK" is free software;
 * you can redistribute it and/or modify it under the terms of the
 * GNU General Public License as published by the Free Software Foundation;
 * either version 2 of the License, or (at your option) any later version. 
 *
 * "H.323 support for ASTERISK" is distributed in the hope that it will be 
 * useful, but WITHOUT ANY WARRANTY; without even the implied warranty
 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details. 
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 
 *
 * $Id: wrapendpoint.cxx,v 1.2 2010/05/04 09:02:57 mmanousos Exp $
 *
 */

/************************************************************************/

#include <ptlib.h>
#include <h323.h>
#include <h323pdu.h>
#include <mediafmt.h>
#include <lid.h>

#define cplusplus
#include "version.h"
#include "wrapper_misc.hxx"
#include "wrapcaps.hxx"
#include "wrapper.hxx"
#include "wrapendpoint.hxx"
#include "wrapconnection.hxx"
#include "asteriskaudio.hxx"

using namespace std;

// External stuff used inside the wrapper library
extern start_logchan_cb		on_start_logical_channel; 
extern clear_con_cb			on_connection_cleared;
extern alert_con_cb			on_connection_alert;
extern h323_exception_cb	on_h323_exception;
extern init_con_cb			on_connection_init;
extern stats_con_cb			on_connection_stats;
extern user_data_cb			on_user_data;

/*******************************************************
 * Class WrapH323EndPoint 
 *******************************************************/

/*****************************************************************************
 * Set terminal type to GATEWAY and add all
 * GW prefixes.
 */
WrapH323EndPoint::WrapH323EndPoint(char **gwprefix_tab, int prefix_num)
	: H323EndPoint()
{
	terminalType = e_GatewayOnly;
	//terminalType = e_TerminalOnly;
	SetSilenceDetectionMode(H323AudioCodec::NoSilenceDetection);
	autoStartReceiveVideo = FALSE;
	autoStartTransmitVideo = FALSE;
	autoStartReceiveFax = FALSE;
	autoStartTransmitFax = FALSE;
	autoCallForward = FALSE;

	if (prefix_num > 0) {
		GWPrefix = new PStringArray(prefix_num, gwprefix_tab);
		if (GWPrefix == NULL) {
			WRAPTRACE(1, "Failed to create GW prefixes list.");
		}
	} else
		GWPrefix = NULL;

	frames_L16 = 30;
	frames_PCMU = 30;
	frames_PCMA = 30;
	frames_G728 = 2;
	frames_G729 = 2;
	frames_G726 = 20;
	frames_G7231 = 2;
	frames_GSM = 4;

#ifdef HAS_OH323MODS
	SetJitterBufferAmount(1, 2); // Decrease jitter buf by 1 frame, increase by 2
#endif

#ifdef HAS_OH323MODS
	WRAPTRACE(1, "Compile-time libraries OpenH323 v" << OPENH323VERSION
				<< " (modified)" << ", PWlib v" << PWLIBVERSION);
#else
	WRAPTRACE(1, "Compile-time libraries OpenH323 v" << OPENH323VERSION
				<< ", PWlib v" << PWLIBVERSION);
#endif
}

WrapH323EndPoint::~WrapH323EndPoint()
{
	if (GWPrefix)
		delete GWPrefix;
	WRAPTRACE(2, "Deleting endpoint.");
}


/*****************************************************************************
 *
 */
void
WrapH323EndPoint::SetEndpointTypeInfo(H225_EndpointType & info) const
{
	H323EndPoint::SetEndpointTypeInfo(info);

	info.m_gateway.IncludeOptionalField(H225_GatewayInfo::e_protocol);
	info.m_gateway.m_protocol.SetSize(1);
	H225_SupportedProtocols &protocol=info.m_gateway.m_protocol[0];
	protocol.SetTag(H225_SupportedProtocols::e_voice);

	if (GWPrefix != NULL) {
		PINDEX as=GWPrefix->GetSize();
		((H225_VoiceCaps &)protocol).m_supportedPrefixes.SetSize(as);
		for (PINDEX p=0; p<as; p++)
			H323SetAliasAddress((*GWPrefix)[p],
					((H225_VoiceCaps &)protocol).m_supportedPrefixes[p].m_prefix);
	}
}

/*****************************************************************************
 * Return the number of frames per packet for the specified format,
 * -1 on failure.
 */
int
WrapH323EndPoint::GetFrames(const OpalMediaFormat format)
{
	int	res;

	res = -1;
	switch (format.GetPayloadType()) {
		case RTP_DataFrame::L16_Mono:
			res = frames_L16;
			break;
		case RTP_DataFrame::PCMU:
			res = frames_PCMU;
			break;
		case RTP_DataFrame::PCMA:
			res = frames_PCMA;
			break;
		case RTP_DataFrame::G726:
			res = frames_G726;
			break;
		case RTP_DataFrame::G728:
			res = frames_G728;
			break;
		case RTP_DataFrame::G729:
			res = frames_G729;
			break;
		case RTP_DataFrame::G7231:
			res = frames_G7231;
			break;
		case RTP_DataFrame::GSM:
			res = frames_GSM;
			break;
		default:
			break;
	}
	WRAPTRACE(5, "Returning " << res);
	return(res);
}

/*****************************************************************************
 * Store the number of frames per packet for the specified format.
 */
void
WrapH323EndPoint::SetFrames(const OpalMediaFormat format, int frames)
{
	WRAPTRACE(5, "Setting " << frames);
	switch (format.GetPayloadType()) {
		case RTP_DataFrame::L16_Mono:
			frames_L16 = frames;
			break;
		case RTP_DataFrame::PCMU:
			frames_PCMU = frames;
			break;
		case RTP_DataFrame::PCMA:
			frames_PCMA = frames;
			break;
		case RTP_DataFrame::G726:
			frames_G726 = frames;
			break;
		case RTP_DataFrame::G728:
			frames_G728 = frames;
			break;
		case RTP_DataFrame::G729:
			frames_G729 = frames;
			break;
		case RTP_DataFrame::G7231:
			frames_G7231 = frames;
			break;
		case RTP_DataFrame::GSM:
			frames_GSM = frames;
			break;
		default:
			break;
	}
}

/*****************************************************************************
 * Convert a OPAL media format to a OpenH323 wrapper library value
 */
cap_type_t
WrapH323EndPoint::GetCodecFromFormat(OpalMediaFormat &format)
{
	cap_type_t	res;

	res = CAP_UNDEFINED;
	switch (format.GetPayloadType()) {
		case RTP_DataFrame::L16_Mono:
			res = LINEAR16;
			break;
		case RTP_DataFrame::PCMU:
			res = G711U;
			break;
		case RTP_DataFrame::PCMA:
			res = G711A;
			break;
		case RTP_DataFrame::G726:
			res = G726;
			break;
		case RTP_DataFrame::G728:
			res = G728;
			break;
		case RTP_DataFrame::G729:
			res = G729;
			break;
		case RTP_DataFrame::G7231:
			res = G7231;
			break;
		case RTP_DataFrame::GSM:
			res = GSM0610;
			break;
		default:
			break;
	}
	return(res);
}

/*****************************************************************************
 * Set the Q.931 cause code of the specified connection.
 */
void
WrapH323EndPoint::SetClearCallCause(const PString & token, int cause)
{
	H323Connection *pH323Con;

	WRAPTRACE(3, "Setting the Q.931 cause code of connection [" << token << "], at " << cause);
	pH323Con = FindConnectionWithLock(token);
	if (pH323Con == NULL) {
		WRAPTRACE(2, "Could not find connection with token " << token);
		return;
	}
	((WrapH323Connection *)pH323Con)->SetQ931Cause(cause);
	pH323Con->Unlock();
	return;
}

/*****************************************************************************
 * Clear the capabilities of the endpoint object.
 */
void
WrapH323EndPoint::RemoveAllCapabilities()
{
	WRAPTRACE(3, "Removing all capabilities of local endpoint.");
	if (capabilities.GetSize())
		capabilities.RemoveAll();
}

/*****************************************************************************
 * Initialize and start an outgoing call.
 */
call_ret_val_t 
WrapH323EndPoint::MakeCall(const PString & dest, PString & token, 
					unsigned int *callReference, user_details_t *ud)
{
	call_ret_val_t	retVal;
	PString			fullAddress;
	H323Connection	*pH323Con;

	fullAddress = dest;
	WRAPTRACE(2, "Making call to " << fullAddress);

	pH323Con = H323EndPoint::MakeCallLocked(fullAddress, token, (void *)ud);
	if (pH323Con == NULL) {
		WRAPTRACE(2, "Error making call to \"" << fullAddress << '"');
		retVal = CALL_START_ER;
		return retVal;
	}
	if (pH323Con != NULL) {
		*callReference = pH323Con->GetCallReference();
		pH323Con->Unlock();
		WRAPTRACE(3, "Call token is " << (const char *)token);
		WRAPTRACE(3, "Call reference is " << *callReference);
		retVal = CALL_START_OK;
	} else {
		WRAPTRACE(2, "Error making call to \"" << fullAddress << '"');
		retVal = CALL_START_ER;
	}
	return retVal;
}

/*****************************************************************************
 * Answer an incoming call.
 */
BOOL
WrapH323EndPoint::AnswerCall(const PString & token)
{
	H323Connection						*pH323Con;
	H323Connection::AnswerCallResponse	resp;

	WRAPTRACE(2, "Request to answer call " << token);
	pH323Con = FindConnectionWithLock(token);
	if (pH323Con == NULL) {
		WRAPTRACE(2, "Could not find connection with token " << token);
		return FALSE;
	}
	resp = H323Connection::AnswerCallNow;
	pH323Con->AnsweringCall(resp);
	pH323Con->Unlock();
	WRAPTRACE(2, "Call answered [" << token << "]");
	return TRUE;
}

/*****************************************************************************
 * Act on a transfer request from a connection.
 */
H323Connection *
WrapH323EndPoint::SetupTransfer(const PString &token, const PString &callerID,
		const PString &remoteParty, PString &newToken, void *)
{
	const char		*transfer;
	H323Connection	*pCon;
	call_details_t	cdet;

	WRAPTRACE(2, "Transfer setup to " << remoteParty);
	transfer = (const char *)remoteParty;

	pCon = FindConnectionWithLock(token);
	if (pCon == NULL) {
		WRAPTRACE(2, "Could not find connection with token " << token);
		return NULL;
	}

	// Invoke the callback function.
	if (on_h323_exception != NULL) {
		cdet.app_id = ((WrapH323Connection *)pCon)->GetAppID();
		cdet.call_reference = pCon->GetCallReference();
		strncpy(cdet.call_token, (const char*)pCon->GetCallToken(), 
						sizeof(cdet.call_token) - 1);
		on_h323_exception(cdet, OH323EXC_CALL_TRANSFER, (char *)transfer);
	} else {
		cout << "H.323 WARNING: No exception handling!" << endl;
	}
	pCon->Unlock();

	return NULL;
}

/*****************************************************************************
 *
 */
BOOL
WrapH323EndPoint::ChangeMode(const PString & token, const PString & newMode)
{
	H323Connection *pH323Con;

	WRAPTRACE(2, "Request to set mode of call token " << token << " in " << newMode);
	pH323Con = FindConnectionWithLock(token);
	if (pH323Con == NULL) {
		WRAPTRACE(2, "Could not find connection with token " << token);
		return FALSE;
	}
	if (pH323Con->RequestModeChange(newMode) != TRUE) {
		WRAPTRACE(2, "Failed to initiate a ModeChange for call " << token);
		pH323Con->Unlock();
		return FALSE;
	}
	pH323Con->Unlock();
	WRAPTRACE(2, "Initiated ModeChange for call with token " << token);
	return TRUE;
}

/*****************************************************************************
 *
 */
BOOL
WrapH323EndPoint::IsConnectionCleared(const PString & token)
{
	H323Connection *pH323Con = NULL;
	BOOL res;

	WRAPTRACE(2, "Checking call [" << token << "]");
	pH323Con = FindConnectionWithLock(token);
	if (pH323Con != NULL) {
		if (((WrapH323Connection*)pH323Con)->GetCallEndReason() == H323Connection::NumCallEndReasons) {
			res = FALSE;
		} else {
			res = TRUE;
		}
		pH323Con->Unlock();
	} else {
		WRAPTRACE(3, "No H.323 connection with token " << token);
		res = FALSE;
	}
	return res;
}

/*****************************************************************************
 *
 */
BOOL
WrapH323EndPoint::ClearCall(const PString & token,
						H323Connection::CallEndReason reason)
{
	WRAPTRACE(2, "Request to clear call [" << token << "]");
	return H323EndPoint::ClearCall(token, reason);
}

/*****************************************************************************
 *
 */
BOOL
WrapH323EndPoint::ClearCallSynchronous(const PString & token,
						H323Connection::CallEndReason reason)
{
	WRAPTRACE(2, "Request to clear call [" << token << "]");
	return H323EndPoint::ClearCallSynchronous(token, reason);
}

/*****************************************************************************
 *
 */
BOOL
WrapH323EndPoint::IndicateCall(const PString & token, indication_t type)
{
	H323Connection *pH323Con = NULL;
	BOOL res;

	pH323Con = FindConnectionWithLock(token);
	if (pH323Con != NULL) {
		switch (type) {
		case IND_RINGING:
			WRAPTRACE(2, "Indicating RINGING on call [" << token << "]");
			pH323Con->AnsweringCall(H323Connection::AnswerCallPending);
			res = TRUE;
			break;
		case IND_BUSY:
			WRAPTRACE(2, "Indicating BUSY on call [" << token << "]");
			pH323Con->ClearCall(H323Connection::EndedByLocalBusy);
			res = TRUE;
			break;
		case IND_CONGESTION:
			WRAPTRACE(2, "Indicating CONGESTION on call [" << token << "]");
			pH323Con->ClearCall(H323Connection::EndedByLocalCongestion);
			res = TRUE;
			break;
		case IND_PROGRESS:
			WRAPTRACE(2, "Indicating PROGRESS on call [" << token << "]");
			pH323Con->AnsweringCall(H323Connection::AnswerCallDeferredWithMedia);
			res = TRUE;
			break;
		default:
			WRAPTRACE(2, "Cannot indicate condition " << type);
			res = TRUE;
			break;
		}
		pH323Con->Unlock();
	} else {
		WRAPTRACE(3, "No H.323 connection with token " << token);
		res = FALSE;
	}
	return res;
}

/*****************************************************************************
 *
 */
void
WrapH323EndPoint::SetGatekeeperTimeToLive(int ttl)
{
	registrationTimeToLive = PTimeInterval(0, ttl);		// ttl in seconds
	WRAPTRACE(3, "Gatekeeper registration TTL set at " << 
					registrationTimeToLive.GetSeconds() << " sec");
}

/*****************************************************************************
 *
 */
void
WrapH323EndPoint::SendUserInput(const PString &token, const PString &input)
{
	H323Connection::SendUserInputModes	mode;
	H323Connection						*pH323Con = NULL;

	pH323Con = FindConnectionWithLock(token);
	if (pH323Con != NULL) {
		pH323Con->SendUserInput(input);
		mode = pH323Con->GetRealSendUserInputMode();
		pH323Con->Unlock();
		WRAPTRACE(3, "Sent user input string (" << input 
							<< ") using mode " << mode);
	} else {
		WRAPTRACE(3, "No H.323 connection with token " << token);
	}
}

/*****************************************************************************
 *
 */
void
WrapH323EndPoint::OnUserInputString(H323Connection & connection, 
			const PString & value)
{
	call_details_t	cdet;
	const char		*message;
	const char		*tones;
	PString			msg;

	WRAPTRACE(3, "Received user input string (" << value << ") from remote");
	if (connection.Lock()) {
		if (value.Left(3) == "MSG") {  // Check for GnomeMeeting message
			msg = value.Mid(3);
			message = (const char *)msg;
			// Invoke the callback function.
			if (on_h323_exception != NULL) {
				cdet.app_id = ((WrapH323Connection &)connection).GetAppID();
				cdet.call_reference = connection.GetCallReference();
				strncpy(cdet.call_token, (const char*)connection.GetCallToken(), 
							sizeof(cdet.call_token) - 1);
				on_h323_exception(cdet, OH323EXC_USER_MESSAGE, (char *)message);
			} else {
				cout << "H.323 WARNING: No exception handling!" << endl;
			}
		} else {
			// Invoke the callback function.
			if (on_h323_exception != NULL) {
				tones = (const char *)value;
				cdet.app_id = ((WrapH323Connection &)connection).GetAppID();
				cdet.call_reference = connection.GetCallReference();
				strncpy(cdet.call_token, (const char*)connection.GetCallToken(), 
							sizeof(cdet.call_token) - 1);
				on_h323_exception(cdet, OH323EXC_USER_INPUT_TONE, (char *)tones);
			} else {
				cout << "H.323 WARNING: No exception handling!" << endl;
			}
		}
		connection.Unlock();
	} else {
		WRAPTRACE(1, "Failed to lock connection!");
	}
}

/*****************************************************************************
 *
 */
unsigned
WrapH323EndPoint::GetBandwidthAvailable()
{
	PStringList conTokens;
	H323Connection *pCon;
	PINDEX i;
	int bwAvail;

	bwAvail = H323EndPoint::GetInitialBandwidth();
	conTokens = H323EndPoint::GetAllConnections();
	for (i=0; i<conTokens.GetSize(); i++) {
		pCon = FindConnectionWithLock(conTokens[i]);
		if (pCon != NULL) {
			bwAvail -= pCon->GetBandwidthUsed();
			pCon->Unlock();
		}
	}
	if (bwAvail < 0)
		bwAvail = 0;
	WRAPTRACE(3, "Available bandwidth: " << bwAvail*100 << "bps, "
					<< "Connection(s): " << i);

	return (unsigned)bwAvail;
}

/*****************************************************************************
 *
 */
void
WrapH323EndPoint::GetConnectionInfo(const PString &token, char *buf, int bufsize)
{
	H323Connection *pCon;
	RTP_Session *pRtp;
	RTP_UDP *pRtpUdp;
	PIPSocket::Address plocal, premote;

	memset(buf, 0, bufsize);
	pCon = FindConnectionWithLock(token);
	if (pCon != NULL) {
		// Get the RTP local and remote addresses
		pRtp = pCon->GetSession(RTP_Session::DefaultAudioSessionID);
		if (pRtp != NULL) {
			pRtpUdp = (RTP_UDP*)pRtp;
			plocal = pRtpUdp->GetLocalAddress();
			premote = pRtpUdp->GetRemoteAddress();
			snprintf(buf, bufsize-1, "%hhu.%hhu.%hhu.%hhu:%d-%hhu.%hhu.%hhu.%hhu:%d", 
						plocal.Byte1(), plocal.Byte2(), plocal.Byte3(), plocal.Byte4(),
						pRtpUdp->GetLocalDataPort(),
						premote.Byte1(), premote.Byte2(), premote.Byte3(), premote.Byte4(),
						pRtpUdp->GetRemoteDataPort());
			WRAPTRACE(3, "[" << token << "] RTP Media: " << buf);
		} else {
			WRAPTRACE(2, "No default audio session ID!");
		}
		pCon->Unlock();
	} else {
		WRAPTRACE(2, "No connection with token " << token);
	}
}

/*****************************************************************************
 *
 */
void
WrapH323EndPoint::OnRTPStatistics(const H323Connection & connection,
								const RTP_Session & session) const
{
	call_details_t cd;
	rtp_stats_t rs;

	// XXX Not active yet!
	return;

//	if (connection.Lock()) {
		cd.app_id = ((WrapH323Connection &)connection).GetAppID();
		cd.call_reference = connection.GetCallReference();
		strncpy(cd.call_token, (const char*)connection.GetCallToken(), 
						sizeof(cd.call_token) - 1);
		rs.packets_sent = session.GetPacketsSent();
		rs.octets_sent = session.GetOctetsSent();
		rs.packets_recv = session.GetPacketsReceived();
		rs.octets_recv = session.GetOctetsReceived();
		rs.packets_lost = session.GetPacketsLost();
		rs.packets_late = session.GetPacketsTooLate();
		rs.packets_ooo = session.GetPacketsOutOfOrder();
		rs.average_send_time = session.GetAverageSendTime();
		rs.max_send_time = session.GetAverageSendTime();
		rs.min_send_time = session.GetMinimumSendTime();
		rs.average_recv_time = session.GetAverageReceiveTime();
		rs.max_recv_time = session.GetMaximumReceiveTime();
		rs.min_recv_time = session.GetMinimumReceiveTime();
		rs.average_jitter = session.GetAvgJitterTime();
		rs.max_jitter = session.GetMaxJitterTime();
		rs.current_jitter_size = session.GetJitterBufferSize();
#ifdef HAS_OH323MODS
		rs.with_rr = session.ReceivedRR();
		rs.reported_fraction_lost = session.GetReportFraction();
		rs.reported_total_lost = session.GetReportTotalLost();
		rs.reported_sequence = session.GetReportSequence();
		rs.reported_jitter = session.GetReportJitter();
#else
		rs.with_rr = 0;
#endif
		/* Pass stats to the application */
		if (on_connection_stats != NULL)
			on_connection_stats(cd, rs);
//		connection.Unlock();
//	} else {
//		WRAPTRACE(1, "Failed to lock connection!");
//	}

	return;
}

/*****************************************************************************
 * This member function overrides the H323EndPoint::SetSoundChannelPlayDevice
 * in order to use our PAsteriskSoundChannel class.
 * XXX There must be a better way to do it.
 */
BOOL
WrapH323EndPoint::SetSoundChannelPlayDevice(const PString & name)
{
	if (PAsteriskSoundChannel::GetDeviceNames(PSoundChannel::Player).GetValuesIndex(name) == P_MAX_INDEX)
		return FALSE;
	soundChannelPlayDevice = name;
	return TRUE;
}

/*****************************************************************************
 * This member function overrides the H323EndPoint::SetSoundChannelRecordDevice
 * in order to use our PAsteriskSoundChannel class.
 * XXX There must be a better way to do it.
 */
BOOL
WrapH323EndPoint::SetSoundChannelRecordDevice(const PString & name)
{
	if (PAsteriskSoundChannel::GetDeviceNames(PSoundChannel::Recorder).GetValuesIndex(name) == P_MAX_INDEX)
		return FALSE;
	soundChannelRecordDevice = name;
	return TRUE;
}

/*****************************************************************************
 * The OpenAudioChannel callback is invoked when a connection is
 * established. The 'on_start_logical_channel' callback which is registered
 * from the application is called and returns the open file descriptor
 * which is used to send/receive voice data.
 */
BOOL 
WrapH323EndPoint::OpenAudioChannel(H323Connection & connection,
		BOOL isEncoding,
		unsigned bufferSize,
		H323AudioCodec & codec)
{
	PString			deviceName, audio_dev_name;
	lchan_dir		dir;
	char			*dev_name;	/* name of the named FIFO */
	call_details_t	cdet;		/* Call details structure is passed to the PBX app. */
	int				fd;
	unsigned int	bufSize, bufNum;
	unsigned int	mediaType;
	unsigned int	frameTime;
	unsigned int	frameNum;
	unsigned int	packetSize;
	cap_type_t		cap_type;
	OpalMediaFormat	mediaFormat;
	PIPSocket::Address plocal, premote;

	dir = (isEncoding == true) ? RECORDER : PLAYER;

	/* Check direction */
	switch (dir) {
		case RECORDER:
			WRAPTRACE(3, "Direction => RECODER, Buffer => " << bufferSize);
			break;
		case PLAYER:
			WRAPTRACE(3, "Direction => PLAYER, Buffer => " << bufferSize);
			break;
		default:
			WRAPTRACE(3, "Direction => UNKNOWN");
			return FALSE;
	}

	if (connection.Lock()) {
		/* Invoke the application callback and get the open
		   socket descriptor from ASTERISK. */
		PString id1 = connection.GetCallIdentifier().AsString();
		PString id2 = connection.GetConferenceIdentifier().AsString();

		memset((char*)&cdet, 0, sizeof(cdet));
		strncpy(cdet.call_id, (const char*)id1, sizeof(cdet.call_id) - 1);
		strncpy(cdet.conf_id, (const char*)id2, sizeof(cdet.call_id) - 1);
		cdet.app_id = ((WrapH323Connection &)connection).GetAppID();
		cdet.call_reference = connection.GetCallReference();
		strncpy(cdet.call_token, (const char*)connection.GetCallToken(), 
						sizeof(cdet.call_token) - 1);
		strncpy(cdet.call_source_alias, (const char *)((WrapH323Connection &)connection).sourceAliases, 
						sizeof(cdet.call_source_alias) - 1);
		strncpy(cdet.call_dest_alias, (const char *)((WrapH323Connection &)connection).destAliases,
						sizeof(cdet.call_dest_alias) - 1);
		strncpy(cdet.call_source_e164, (const char *)((WrapH323Connection &)connection).sourceE164,
						sizeof(cdet.call_source_e164) - 1);
		strncpy(cdet.call_dest_e164, (const char *)((WrapH323Connection &)connection).destE164,
						sizeof(cdet.call_dest_e164) - 1);
		strncpy(cdet.remote_app, (const char *)connection.GetRemoteApplication(),
						sizeof(cdet.remote_app) - 1);
		if (connection.GetSignallingChannel() != NULL) {
			(connection.GetSignallingChannel()->GetRemoteAddress()).GetIpAddress(premote);
			(connection.GetSignallingChannel()->GetLocalAddress()).GetIpAddress(plocal);
			snprintf(cdet.local_addr, sizeof(cdet.local_addr)-1, 
						"%s", (const char *)plocal.AsString());
			snprintf(cdet.remote_addr, sizeof(cdet.remote_addr)-1, 
						"%s", (const char *)premote.AsString());
		}

		mediaFormat = codec.GetMediaFormat();
		mediaType = mediaFormat.GetPayloadType();
		cap_type = GetCodecFromFormat(mediaFormat);
		if (cap_type == CAP_UNDEFINED) {
			WRAPTRACE(2, "Unknown/Unsupported media format" << mediaFormat);
			connection.Unlock();
			return FALSE;
		}
#ifdef	USE_OLD_CAPABILITIES_API
		if (codec.IsDescendant(H323_LIDCodec::Class())
			|| codec.IsDescendant(Wrap_G726_Codec::Class())) {
#else
		if ((PIsDescendant(&codec, H323_LIDCodec))
			|| (PIsDescendant(&codec, Wrap_G726_Codec))) {
#endif
			codec.SetSilenceDetectionMode(H323AudioCodec::NoSilenceDetection);
			WRAPTRACE(2, "Media format: FrameSize " << mediaFormat.GetFrameSize() 
				<< ", FrameTime " << mediaFormat.GetFrameTime()
				<< ", TimeUnits " << mediaFormat.GetTimeUnits());
			WRAPTRACE(2, "Codec info: FrameRate " << codec.GetFrameRate());
			frameTime = mediaFormat.GetFrameTime() / mediaFormat.GetTimeUnits();
			if (frameTime <= 0) {
				WRAPTRACE(2, "Cannot determine frame rate (" << frameTime << ")");
				connection.Unlock();
				return FALSE;
			}
			// Special case for G.711 (see codecs.cxx and lid.cxx of openh323)
			if ((mediaType == RTP_DataFrame::PCMU) || (mediaType == RTP_DataFrame::PCMA)) {
				packetSize = bufferSize/2;
				bufSize = bufferSize;
			// Special case for G.726 (for the same reasons as in G.711)
			} else if (mediaType == RTP_DataFrame::G726) {
				packetSize = (bufferSize * ((Wrap_G726_Codec &)codec).GetSpeed()) / (2*8);
				bufSize = bufferSize;
			// Normal case
			} else {
				packetSize = mediaFormat.GetFrameSize();
				bufSize = mediaFormat.GetFrameSize();
			}
			if (bufSize <= 0)
				bufSize = bufferSize;
			bufNum = 1;
			frameNum = (packetSize / mediaFormat.GetFrameSize());
			WRAPTRACE(2, "Packet size: " << packetSize);
			WRAPTRACE(2, "Frames per packet: " << frameNum);
			WRAPTRACE(2, "LID Codec " << mediaFormat);
		} else {
			/* XXX Not used XXX */
			connection.Unlock();
			return FALSE;
		}

		if (on_start_logical_channel == NULL) {
			cout << "H.323 WARNING: No audio setup handling!" << endl;
			connection.Unlock();
			return FALSE;
		}
		dev_name = NULL;
		//dev_name = on_start_logical_channel(cdet, dir, bufSize, cap_type, &fd, rtp_addr, rtp_port);
		dev_name = on_start_logical_channel(cdet, dir, bufSize, cap_type, &fd);
		if (dev_name == NULL) {
			WRAPTRACE(2, "Could not open a suitable sound channel with application.");
			connection.Unlock();
			return FALSE;
		}
		connection.Unlock();
	} else {
		WRAPTRACE(1, "Failed to lock connection."); 
		return FALSE;
	}

	WRAPTRACE(3, "The sound channel with the application is " << dev_name 
						<< "(fd=" << fd << ")");

	deviceName = "Asterisk";

	PAsteriskSoundChannel * soundChannel = new PAsteriskSoundChannel;

	/* Pass the socket descriptor for read or write according to isEncoding value */
	if (soundChannel->Open(deviceName, fd, 
				isEncoding ? PSoundChannel::Recorder : PSoundChannel::Player, 
				1, mediaType, frameTime, frameNum, packetSize)) {
		WRAPTRACE(3, "Opened sound channel \"" << deviceName
			<< "\" for " << (isEncoding ? "record" : "play")
			<< "ing using " << bufNum
			<< 'x' << bufSize << " byte buffers.");
		soundChannel->SetBuffers(bufSize, bufNum);

		/* Attaches the soundChannel on the socket with the selected codec */
		/* Delete sound channel on closing codec */
		return codec.AttachChannel(soundChannel, TRUE);
	}

	WRAPTRACE(2, "Could not open sound channel \"" << deviceName
	   << "\" for " << (isEncoding ? "record" : "play")
	   << "ing: " << soundChannel->GetErrorText());
	WRAPTRACE(2, "Closed sound channel fd " << fd);
	close(fd);

	delete soundChannel;
	return FALSE;
}

#if 0
/*****************************************************************************
 *
 */
/** 
 * This callback function is invoked once upon creation of each
 * channel for the two directions of an H323 session 
 */
BOOL 
WrapH323EndPoint::OnStartLogicalChannel(H323Connection & connection, H323Channel & channel)
{
	//H323Channel *pChan1, *pChan2;

	if (!H323EndPoint::OnStartLogicalChannel(connection, channel))
		return FALSE;

	//if (GetBandwidthAvailable() == 0) {
	//	connection.CloseAllLogicalChannels(TRUE);
	//	connection.CloseAllLogicalChannels(FALSE);
	//	return FALSE;
	//}

	WRAPTRACE(2, "Started logical channel [" << connection.GetCallToken() << "] : " 
		<< ((channel.GetDirection()==H323Channel::IsTransmitter)?"sending ":((channel.GetDirection()==H323Channel::IsReceiver)?"receiving ":" "))
		<< (const char *)(channel.GetCapability()).GetFormatName());

#if 0
	RTP_Session *pRtp;
	pRtp = connection.GetSession(RTP_Session::DefaultAudioSessionID);
	if (pRtp != NULL) {
		cout << "Local " << ((RTP_UDP*)pRtp)->GetLocalAddress() << ":"
						 << ((RTP_UDP*)pRtp)->GetLocalDataPort() << ", "
			 << "Remote " << ((RTP_UDP*)pRtp)->GetRemoteAddress() << ":"
						 << ((RTP_UDP*)pRtp)->GetRemoteDataPort() << endl;
	} else {
		cout << " XXXX No audio session  ???" << endl;
	}
#endif

#if 0
	H323_ExternalRTPChannel & ext_chan = (H323_ExternalRTPChannel &)channel;
	PIPSocket::Address ip;
	WORD port;
	if (ext_chan.GetRemoteAddress(ip, port) != TRUE)
		WRAPTRACE(2, "Failed to get remote address!");
	else
		WRAPTRACE(2, "Remote media: IP " << ip << ", PORT " << port);
#endif

	// adjust the count of channels we have open
	if (channel.GetDirection()==H323Channel::IsTransmitter) {
		//if (channelMutex->Wait(WMUTEX_INFO, 3000) == TRUE) {	// 3 sec max
			WRAPTRACE(2, "TxFrames = " << channel.GetCapability().GetTxFramesInPacket());
			channelsOpen++;
			WRAPTRACE(3, "channelsOpen = " << channelsOpen);
#if 0
			// XXX It doesn't work when using "fastStart"
			channel.SetPause(TRUE);
			pChan1 = connection.FindChannel(RTP_Session::DefaultAudioSessionID, TRUE);
			pChan2 = connection.FindChannel(RTP_Session::DefaultAudioSessionID, FALSE);
			if ((pChan1 != NULL) && (pChan2 != NULL)) {
				pChan1->SetPause(FALSE);
				pChan2->SetPause(FALSE);
				WRAPTRACE(3, "Connection " << connection.GetCallToken() 
							<< " has both channels active.");
			}
#endif
			//channelMutex->Signal(WMUTEX_INFO);
			return TRUE;
		//}
	}
	if (channel.GetDirection()==H323Channel::IsReceiver) {
		//if (channelMutex->Wait(WMUTEX_INFO, 3000) == TRUE) {	// 3 sec max
			WRAPTRACE(2, "RxFrames = " << channel.GetCapability().GetRxFramesInPacket());
			channelsOpen++;
			WRAPTRACE(3, "channelsOpen = " << channelsOpen);
#if 0
			// XXX It doesn't work when using "fastStart"
			channel.SetPause(TRUE);
			pChan1 = connection.FindChannel(RTP_Session::DefaultAudioSessionID, TRUE);
			pChan2 = connection.FindChannel(RTP_Session::DefaultAudioSessionID, FALSE);
			if ((pChan1 != NULL) && (pChan2 != NULL)) {
				pChan1->SetPause(FALSE);
				pChan2->SetPause(FALSE);
				WRAPTRACE(3, "Connection " << connection.GetCallToken() 
							<< " has both channels active.");
			}
#endif
			//channelMutex->Signal(WMUTEX_INFO);
			return TRUE;
		//}
	}
	WRAPTRACE(2, "Failed to start logical channel [" << connection.GetCallToken()
				<< "]");

	return FALSE;
}

/*****************************************************************************
 *
 */
void 
WrapH323EndPoint::OnClosedLogicalChannel(H323Connection & connection, 
				const H323Channel & channel)
{
	WRAPTRACE(2, "Closed logical channel [" << connection.GetCallToken() << "].");

	// adjust the count of channels we have open
	//channelMutex->Wait(WMUTEX_INFO, WMUTEX_WAIT_FOREVER);
	channelsOpen--;
	WRAPTRACE(3, "channelOpen = " << channelsOpen);
	//channelMutex->Signal(WMUTEX_INFO);
	H323EndPoint::OnClosedLogicalChannel(connection, channel);
}
#endif

/*****************************************************************************
 *
 */
BOOL
WrapH323EndPoint::OnCallTransferInitiate(H323Connection & connection,
				const PString & remoteParty)
{
	WRAPTRACE(2, "Call transfer initiated [" << connection.GetCallToken() << "].");
	return TRUE;
}

/*****************************************************************************
 *
 */
void WrapH323EndPoint::OnConnectionEstablished(H323Connection & connection, 
				const PString & token)
{
	call_details_t	cdet;		// Call details structure is passed to the app
	char buf[256];

	WRAPTRACE(3, "Connection [" << token << "] established.");
	if (connection.Lock()) {
		if (on_h323_exception != NULL) {
			// Fill the structure
			cdet.app_id = ((WrapH323Connection &)connection).GetAppID();
			cdet.call_reference = connection.GetCallReference();
			strncpy(cdet.call_token, (const char*)connection.GetCallToken(), 
						sizeof(cdet.call_token) - 1);
			GetConnectionInfo(token, buf, sizeof(buf));
			on_h323_exception(cdet, OH323EXC_CALL_ESTABLISHED, buf);
		} else {
			cout << "H.323 WARNING: No call exception handling!" << endl;
		}
		connection.Unlock();
	} else {
		WRAPTRACE(1, "Failed to lock connection.");
	}
}

/*****************************************************************************
 * The OnConnectionCleared callback function is called upon the dropping of an 
 * established H323 connection. Among others it calls the on_connection_cleared 
 * function which is installed previously by the application and it is 
 * responsible for cleaning of any internal channel related structuring.
 */
void 
WrapH323EndPoint::OnConnectionCleared(H323Connection & connection, 
				const PString & token)
{
	PString			remoteName = '"' + connection.GetRemotePartyName() + '"';
	PTime 			connectTime = connection.GetConnectionStartTime();
	PTimeInterval	callDuration;
	call_details_t	cdet;

	WRAPTRACE(2, "Connection [" << token << "] closed.");

	/* Invoke the application registered callback */
	cdet.app_id = ((WrapH323Connection &)connection).GetAppID();
	cdet.call_reference = connection.GetCallReference();
	strncpy(cdet.call_token, (const char*)token, 
						sizeof(cdet.call_token) - 1);
	cdet.call_end_reason = connection.GetCallEndReason();
	cdet.call_q931_cause = connection.GetQ931Cause();
	if (cdet.call_q931_cause == Q931::ErrorInCauseIE)
		cdet.call_q931_cause = 0;
	if (connectTime.GetTimeInSeconds() != 0) {
		callDuration = connection.GetConnectionEndTime() - connection.GetConnectionStartTime();
		cdet.call_duration = (int)callDuration.GetSeconds();
	} else {
		cdet.call_duration = 0;
	}
	if (on_connection_cleared != NULL)
		on_connection_cleared(cdet);
	else
		cout << "H.323 WARNING: No callback for call clearing!" << endl;

	switch (connection.GetCallEndReason()) {
		case H323Connection::EndedByCallForwarded :
			break;
		case H323Connection::EndedByRemoteUser :
			WRAPTRACE(2, remoteName << " has cleared the call");
			break;
		case H323Connection::EndedByCallerAbort :
			WRAPTRACE(2, remoteName << " has stopped calling");
			break;
		case H323Connection::EndedByRefusal :
			WRAPTRACE(2, remoteName << " did not accept your call");
			break;
		case H323Connection::EndedByRemoteBusy :
			WRAPTRACE(2, remoteName << " was busy");
			break;
		case H323Connection::EndedByRemoteCongestion :
			WRAPTRACE(2, "Congested link to " << remoteName);
			break;
		case H323Connection::EndedByNoAnswer :
			WRAPTRACE(2, remoteName << " did not answer your call");
			break;
		case H323Connection::EndedByTransportFail :
			WRAPTRACE(2, "Call with " << remoteName << " ended abnormally");
			break;
		case H323Connection::EndedByCapabilityExchange :
			WRAPTRACE(2, "Could not find common codec with " << remoteName);
			break;
		case H323Connection::EndedByNoAccept :
			WRAPTRACE(2, "Did not accept incoming call from " << remoteName);
			break;
		case H323Connection::EndedByAnswerDenied :
			WRAPTRACE(2, "Refused incoming call from " << remoteName);
			break;
		case H323Connection::EndedByNoUser :
			WRAPTRACE(2, "Gatekeeper could not find user " << remoteName);
			break;
		case H323Connection::EndedByNoBandwidth :
			WRAPTRACE(2, "Call to " << remoteName << " aborted, insufficient bandwidth.");
			break;
		case H323Connection::EndedByUnreachable :
			WRAPTRACE(2, remoteName << " could not be reached.");
			break;
		case H323Connection::EndedByHostOffline :
			WRAPTRACE(2, remoteName << " is not online.");
			break;
		case H323Connection::EndedByNoEndPoint :
			WRAPTRACE(2, "No phone running for " << remoteName);
			break;
		case H323Connection::EndedByConnectFail :
			WRAPTRACE(2, "Transport error calling " << remoteName);
			break;
		default :
			WRAPTRACE(2, "Call with " << remoteName << " completed");
	}
}

/*****************************************************************************
 *
 */
H323Connection *
WrapH323EndPoint::CreateConnection(unsigned callReference, void *userData,
			H323Transport *transport, H323SignalPDU *setupPDU)
{
	user_details_t ud;
	void *our_userData;
	int res, i;
	H323TransportAddress remote;
	PIPSocket::Address addr;
	WORD port;
	PString tmp;

	WRAPTRACE(4, "Creating a H323Connection [" << callReference << "]");

	our_userData = userData;
	if (setupPDU != NULL) {
		WRAPTRACE(2, "Incoming call");

		/* Setup 'userData' and pass them down to the connection */
		if (on_user_data != NULL) {
			ud.incoming_call = 1;
			/* Get endpoint IP */
			if (transport != NULL) {
				(transport->GetRemoteAddress()).GetIpAndPort(addr, port);
				snprintf(ud.ipaddr, sizeof(ud.ipaddr)-1, 
							"%s", (const char *)addr.AsString());
			} else {
				WRAPTRACE(2, "Incoming connection with no transport!");
				ud.ipaddr[0] = '\0';
			}
			/* Get user name - display name */
			if (!(((setupPDU->GetQ931()).GetDisplayName()).IsEmpty())) {
				strncpy(ud.username, (const char*)((setupPDU->GetQ931()).GetDisplayName()), 
								sizeof(ud.username) - 1);
			} else {
				WRAPTRACE(2, "Incoming connection with no display name in SETUP.");
				ud.username[0] = '\0';
			}
			/* Get Calling party number */
			(setupPDU->GetQ931()).GetCallingPartyNumber(tmp, 
							(unsigned *)&(ud.num_plan), 
							(unsigned *)&(ud.ton), 
							(unsigned *)&(ud.presentation), 
							(unsigned *)&(ud.screening));
			if (!tmp.IsEmpty()) {
				strncpy(ud.calling_num, (const char*)tmp, sizeof(ud.calling_num) - 1);
				/* Normalize calling number */
				i = 0;
				while (ud.calling_num[i] != '\0') {
					if ((ud.calling_num[i] == ' ') ||
						(ud.calling_num[i] == ',') ||
						(ud.calling_num[i] == '[') ||
						(ud.calling_num[i] == '<') ||
						(ud.calling_num[i] == '\n') ||
						(ud.calling_num[i] == '\r') ||
						(ud.calling_num[i] == '\t')) {
						ud.calling_num[i] = '\0';
						break;
					}
					i++;
				}
				if (ud.username[0] == '\0')
					strncpy(ud.username, (const char *)(ud.calling_num), sizeof(ud.username) - 1);
			} else {
				WRAPTRACE(2, "Incoming connection with no calling party number in SETUP.");
				ud.calling_num[0] = '\0';
			}
			tmp.MakeEmpty();
			/* Get Called party number */
			(setupPDU->GetQ931()).GetCalledPartyNumber(tmp);
			if (!tmp.IsEmpty()) {
				strncpy(ud.called_num, (const char*)tmp, sizeof(ud.called_num) - 1);
				/* Normalize called number */
				i = 0;
				while (ud.called_num[i] != '\0') {
					if ((ud.called_num[i] == ' ') ||
						(ud.called_num[i] == ',') ||
						(ud.called_num[i] == '[') ||
						(ud.called_num[i] == '<') ||
						(ud.called_num[i] == '\n') ||
						(ud.called_num[i] == '\r') ||
						(ud.called_num[i] == '\t')) {
						ud.called_num[i] = '\0';
						break;
					}
					i++;
				}
			} else {
				WRAPTRACE(2, "Incoming connection with no called party number in SETUP.");
				ud.called_num[0] = '\0';
			}
			tmp.MakeEmpty();
			/* Get Redirecting number */
			(setupPDU->GetQ931()).GetRedirectingNumber(tmp);
			if (!tmp.IsEmpty()) {
				strncpy(ud.redirect_num, (const char*)tmp, sizeof(ud.redirect_num) - 1);
				/* Normalize called number */
				i = 0;
				while (ud.redirect_num[i] != '\0') {
					if ((ud.redirect_num[i] == ' ') ||
						(ud.redirect_num[i] == ',') ||
						(ud.redirect_num[i] == '[') ||
						(ud.redirect_num[i] == '<') ||
						(ud.redirect_num[i] == '\n') ||
						(ud.redirect_num[i] == '\r') ||
						(ud.redirect_num[i] == '\t')) {
						ud.redirect_num[i] = '\0';
						break;
					}
					i++;
				}
			} else {
				WRAPTRACE(2, "Incoming connection with no redirecting number in SETUP.");
				ud.redirect_num[0] = '\0';
			}
			res = on_user_data(&ud);
			if (res < 0)
				our_userData = NULL;
			else
				our_userData = (void *)&ud;
		}
	}

	return new WrapH323Connection(*this, callReference, our_userData);
}

// End of file //////////////////////////////////////////////////////////////
